package se.perp.c4.control;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import se.perp.c4.control.iface.IGamePieceListener;
import se.perp.c4.control.iface.IPlacementButtonListener;
import se.perp.c4.control.iface.IPlacementCompleteListener;
import se.perp.c4.model.Actor;
import se.perp.c4.model.Actor.EActor;
import se.perp.c4.model.Actor.Side;
import se.perp.c4.model.ActorCollection;
import se.perp.c4.model.Territory;
import se.perp.c4.model.Territory.ETerritory;
import se.perp.c4.model.TerritoryCollection;
import se.perp.c4.ui.ActorViewer;
import se.perp.c4.ui.GamePiece;
import se.perp.c4.ui.PieceCollection;
import se.perp.c4.ui.PieceOrderer;
import se.perp.c4.ui.PlacementPanel;
import se.perp.c4.ui.RegionBounds;

import static se.perp.c4.model.Territory.ETerritory.*;

class PlacementHandler implements IGamePieceListener, IPlacementButtonListener {

    private PlacementPanel placementPanel;
    private ActorCollection actorColl;
    private TerritoryCollection terrColl;
    private PieceCollection pieceColl;
    private ActorViewer actorViewer;
    private Side side;
    private ETerritory[] validTerritories;
    private Rectangle unplacedRect;
    private Map<EActor, Point> unplacedPositions = new HashMap<EActor, Point>();
    private List<EActor> unplacedActors = new ArrayList<EActor>(),  placedActors = new ArrayList<EActor>();
    private final int GAP = 10; // between unplaced pieces
    private IPlacementCompleteListener completeListener = null;

    public PlacementHandler(
            PlacementPanel p,
            Side side,
            Rectangle unplacedRect,
            ActorCollection ac,
            TerritoryCollection tc,
            PieceCollection pc,
            ActorViewer av) {
        placementPanel = p;
        this.side = side;
        this.unplacedRect = unplacedRect;
        actorColl = ac;
        terrColl = tc;
        pieceColl = pc;
        actorViewer = av;

        if (side == Side.LIGHT) {
            validTerritories = new ETerritory[]{SHIRE, ARTHEDAIN, CARDOLAN, RHUDAUR, EREGION, ENEDWAITH};
            for (EActor ea : Actor.LIGHT_ACTORS) {
                unplacedActors.add(ea);
            }
        } else {
            validTerritories = new ETerritory[]{MORDOR, DAGORLAD, GONDOR, MIRKWOOD, FANGORN, ROHAN};
            for (EActor ea : Actor.DARK_ACTORS) {
                unplacedActors.add(ea);
            }
        }

        setUnplacedPositions();

        placementPanel.getOkButton().setEnabled(false);
        placementPanel.getResetButton().setEnabled(false);
    }

    public void setPlacementCompleteListener(IPlacementCompleteListener l) {
        completeListener = l;
    }

    private boolean isValid(ETerritory et) {
        for (ETerritory v : validTerritories) {
            if (v == et) {
                return true;
            }
        }
        return false;
    }

    public void pieceDropped(GamePiece source, Point where) {
        ETerritory et = RegionBounds.TerritoryIdAt(where);

        if (!isValid(et)) {
            if (unplacedRect.contains(where)) {
                setAsUnplaced(source.getActor());
            } else {
                source.snapback();
            }
            return;
        }

        Territory t = terrColl.get(et);
        if (t.isFull()) {
            source.snapback();
            return;
        }

        setAsPlaced(source.getActor(), et);
    }

    private boolean placementIsOk() {
        for (ETerritory et : validTerritories) {
            Territory t = terrColl.get(et);
            if (t.type == Territory.Type.HOME && t.getOccupantCount() != 4) {
                return false;
            } else if (t.type != Territory.Type.HOME && t.getOccupantCount() != 1) {
                return false;
            }
        }
        return true;
    }

    private void setAsPlaced(EActor ea, ETerritory where) {
        Actor a = actorColl.get(ea);

        if (a.where == ETerritory.NOWHERE) {

            Territory t = terrColl.get(where);
            t.addActor(a);
            PieceOrderer.orderIn(terrColl, pieceColl, t);
            unplacedActors.remove(ea);
            placedActors.add(ea);

        } else {
            Territory from = terrColl.get(a.where);
            Territory to = terrColl.get(where);

            terrColl.moveActor(a, where);

            PieceOrderer.orderIn(terrColl, pieceColl, from);
            PieceOrderer.orderIn(terrColl, pieceColl, to);
        }

        if (unplacedActors.isEmpty() && placementIsOk()) {
            placementPanel.getOkButton().setEnabled(true);
        } else {
            placementPanel.getOkButton().setEnabled(false);
        }
        placementPanel.getResetButton().setEnabled(true);

    }

    public void pieceClicked(GamePiece source, boolean doubleClick, int keyModifiers) {
        if (source != null) {
            Actor a = actorColl.get(source.getActor());
            actorViewer.setActor(a);

            boolean shiftPressed = (keyModifiers & KeyEvent.SHIFT_DOWN_MASK) != 0;
            boolean ctrlPressed = (keyModifiers & KeyEvent.CTRL_DOWN_MASK) != 0;

            if (ctrlPressed && shiftPressed) {
                randomClicked();
            } else if (doubleClick || shiftPressed) {
                source.setNormalBackground();
                if (a.where == ETerritory.NOWHERE) {
                    placeInFirstAvailableTerritory(a.who);
                } else {
                    setAsUnplaced(a.who);
                }
            }
        }
    }

    public void pieceDragged(GamePiece source, Point where) {
        source.getParent().setComponentZOrder(source, 0);
    }

    private void placeInFirstAvailableTerritory(EActor who) {
        for (ETerritory et : validTerritories) {
            Territory t = terrColl.get(et);

            boolean homeAndNotFull = t.type == Territory.Type.HOME && !t.isFull();
            boolean notHomeAndEmpty = t.type == Territory.Type.NORMAL && t.getOccupantCount() == 0;

            if (homeAndNotFull || notHomeAndEmpty) {
                setAsPlaced(who, et);
                return;
            }
        }
        throw new RuntimeException("There should at least be one territory available for double-click auto placement!");
    }

    private void setAsUnplaced(EActor ea) {
        Actor a = actorColl.get(ea);
        GamePiece gp = pieceColl.get(ea);

        if (a.where != ETerritory.NOWHERE) {
            Territory t = terrColl.get(a.where);
            t.removeActor(a);
            PieceOrderer.orderIn(terrColl, pieceColl, t);
            placedActors.remove(ea);
            unplacedActors.add(ea);
        }

        gp.setLocation(unplacedPositions.get(ea));
        gp.remember();

        if (placedActors.isEmpty()) {
            placementPanel.getResetButton().setEnabled(false);
        }
        placementPanel.getOkButton().setEnabled(false);
    }

    private void setUnplacedPositions() {
        Collections.sort(unplacedActors, new Comparator<EActor>() {

            public int compare(EActor a, EActor b) {
                return a.toString().compareTo(b.toString());
            }
        });

        int midX = unplacedRect.x + unplacedRect.width / 2;
        for (int y = unplacedRect.y, i = 0; i < unplacedActors.size(); i++) {
            EActor ea = unplacedActors.get(i);
            GamePiece gp = pieceColl.get(ea);
            Point p = new Point(midX - gp.getWidth() / 2, y);
            gp.setLocation(p);
            gp.remember();
            unplacedPositions.put(ea, p);
            y += gp.getHeight() + GAP;
        }
    }

    public void okClicked() {

        if (completeListener != null) {
            Map<EActor, ETerritory> placement = new HashMap<EActor, ETerritory>();
            Map<EActor, Point> positions = new HashMap<EActor, Point>();

            for (EActor ea : placedActors) {
                Actor a = actorColl.get(ea);
                placement.put(ea, a.where);
                GamePiece gp = pieceColl.get(ea);
                positions.put(ea, gp.getLocation());
            }

            completeListener.placementComplete(placement, positions);
        }
    }

    public void resetClicked() {
        while (!placedActors.isEmpty()) {
            EActor ea = placedActors.get(0);
            setAsUnplaced(ea);
        }

        placementPanel.getOkButton().setEnabled(false);
        placementPanel.getResetButton().setEnabled(false);
    }

    public void randomClicked() {
        while (!placedActors.isEmpty()) {
            EActor ea = placedActors.get(0);
            setAsUnplaced(ea);
        }

        for (ETerritory et : validTerritories) {

            if (et == MORDOR || et == SHIRE) {
                for (int i = 0; i < 4; i++) {
                    int rand = (int) (Math.random() * unplacedActors.size());
                    EActor ea = unplacedActors.get(rand);
                    setAsPlaced(ea, et);
                }
            } else {
                int rand = (int) (Math.random() * unplacedActors.size());
                EActor ea = unplacedActors.get(rand);
                setAsPlaced(ea, et);
            }
        }
    }

    public void pieceReleased(GamePiece source) {
        // do nothing
    }
}
